﻿'******************************************************************************
' Engenharia Elétrica - Instituto Unificado de Ensino Superior Objetivo - IUESO
' TCC: APLICAÇÃO E TRANSMISSÃO DE DADOS EM POWER LINE COMUNICATION
' Descrição: Programa para comunicação serial utilizando o modem PLC
' ALUNOS:
'       Harumi Gondo
'       Jonatas Evaristo Rodrigues dos Santos
'       Vinicius Alves Maia
'******************************************************************************

'Bibliotecas utilizadas
Imports System.IO.Ports
Imports System.Windows.Forms

'*** Links de documentação das bibliotecas ************************************
' http://msdn.microsoft.com/en-us/library/system.io.ports.serialport%28v=VS.90%29.aspx
' http://msdn.microsoft.com/pt-br/library/system.windows.forms.timer%28v=VS.90%29.aspx
'******************************************************************************

'Programa principal
Public Class FormPrincipal
    'Versão do programa
    Public Const VERSAO As String = "1.0.0.0"
    Public Const TITULO As String = "Aplicação e transmissão de dados em PLC"
    Public Const BAUDRATE As Integer = 300
    'Cria o objeto para comunicação serial com tratamento de eventos
    Private WithEvents PortaSerial As New SerialPort
    'Temporizador para envio do ping
    Private WithEvents PingTemporizador As New Timer
    'Numero sequencial enviado pelo ping
    Private PingSeguencial As Byte = 0
    'Endereços do Servidor e do Cliente
    Private Const EnderecoServidor As Byte = &H1
    Private Const EnderecoCliente As Byte = &H2

    'Enumerações utilizadas no progama
    Private Enum Rele As Byte
        Rele1 = &H1
        Rele2 = &H2
        Rele3 = &H3
    End Enum

    Private Enum Comando As Byte
        AtivarRele = &H1
        DesativarRele = &H2
        SoliciatarStatusRele = &H3
        RespostaStatusReleAtivado = &H4
        RespostaStatusReleDesativado = &H5
        Ping = &H6
        RespostaPing = &H7
    End Enum

    'Estrutura utilizada
    Private Structure StrFrame
        Dim EndDestino As Byte
        Dim EndRemetente As Byte
        Dim Comando As Comando
        Dim Dados As Byte
        Dim CheckSum As Byte
        Public Overrides Function ToString() As String
            Return String.Format("[{0:X2}-{1:X2}-{2:X}-{3:X2}-{4:X2}]", EndDestino, EndRemetente, Comando, Dados, CheckSum)
        End Function
        Public Function ToBytes() As Byte()
            Dim Bytes(4) As Byte
            Bytes(0) = EndDestino
            Bytes(1) = EndRemetente
            Bytes(2) = Comando
            Bytes(3) = Dados
            Bytes(4) = CheckSum
            Return Bytes
        End Function
    End Structure

    'Sub-rotina de interrupção do temporizador para envio do Comando de ping
    Private Sub PingTemporizador_Tick(ByVal Sender As Object, ByVal e As EventArgs) Handles PingTemporizador.Tick
        EnviarFrame(Comando.Ping, PingSeguencial)

        'Se a contagem chegar ao final reinicializa.
        If PingSeguencial = 255 Then
            PingSeguencial = 0
        Else
            PingSeguencial += 1
        End If
    End Sub

    'Sub-rotina execultada antes de fechar o programa
    Private Sub FormPrincipal_FormClosing(ByVal sender As Object, ByVal e As System.Windows.Forms.FormClosingEventArgs) Handles Me.FormClosing
        If PortaSerial.IsOpen Then PortaSerial.Close()
    End Sub

    'Executa ao abrir o aplicativo.
    Private Sub FormPrincipal_Load(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles MyBase.Load
        Dim Portas As String()

        AbilitarBotoes(False, False)
        Me.Text = "ComPLC - " & TITULO

        'Mensagem Inicial
        TBLog.Text &= "*******************************************************************************" & vbCrLf
        TBLog.Text &= "Trabalho de conclusão de curso: " & TITULO & " 2011" & vbCrLf
        TBLog.Text &= "Engenharia Elétrica 2011 - IUESO" & vbCrLf
        TBLog.Text &= "*******************************************************************************" & vbCrLf

        'Encontra as portar disponíveis no computador
        MensagemLog(0, "SerialPort", "Procurando portas disponiveis...")
        Portas = SerialPort.GetPortNames()

        If Portas.ToList.Count = 0 Then
            MensagemLog(1, "Portas", "Nenhuma porta serial encontrada!")
            MsgBox("Nenhuma porta serial encontrada ou disponivel!", MsgBoxStyle.Critical, "Alerta...")
            Return
        End If

        MensagemLog(1, "Portas", "Portas seriais encontradas...")
        For Each Porta As String In Portas
            CBXPortas.Items.Add(Porta)
            MensagemLog(2, "Porta:", Porta)
        Next

        'Preenche a caixa de seleção com a primeira porta encontrada
        CBXPortas.Text = CBXPortas.Items(0).ToString
        TBLog.Text &= "*******************************************************************************" & vbCrLf
        TBLog.SelectionStart = TBLog.Text.Length

        'Configura o temporizador de ping para 1 s
        PingTemporizador.Enabled = False
        PingTemporizador.Interval = 1000
    End Sub

    Private Sub MenuItemSair_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles MenuItemSair.Click
        Me.Close()
    End Sub
    Private Sub MenuItemSobre_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles MenuItemSobre.Click
        AboutBox.ShowDialog()
    End Sub

    Private Sub CBRele1_Click(ByVal sender As Object, ByVal e As System.EventArgs) Handles CBRele1.Click
        CBRele1.Enabled = False
        If CBRele1.CheckState = CheckState.Checked Then
            EnviarFrame(Comando.AtivarRele, Rele.Rele1)
            Led1.BackColor = Color.Lime
            EnviarFrame(Comando.SoliciatarStatusRele, Rele.Rele1)
        Else
            EnviarFrame(Comando.DesativarRele, Rele.Rele1)
            Led1.BackColor = Color.DarkGreen
            EnviarFrame(Comando.SoliciatarStatusRele, Rele.Rele1)
        End If
        CBRele1.Enabled = True
    End Sub
    Private Sub CBRele2_Click(ByVal sender As Object, ByVal e As System.EventArgs) Handles CBRele2.Click
        CBRele2.Enabled = False
        If CBRele2.CheckState = CheckState.Checked Then
            EnviarFrame(Comando.AtivarRele, Rele.Rele2)
            Led2.BackColor = Color.Lime
            EnviarFrame(Comando.SoliciatarStatusRele, Rele.Rele2)
        Else
            EnviarFrame(Comando.DesativarRele, Rele.Rele2)
            Led2.BackColor = Color.DarkGreen
            EnviarFrame(Comando.SoliciatarStatusRele, Rele.Rele2)
        End If
        CBRele2.Enabled = True
    End Sub
    Private Sub CBRele3_Click(ByVal sender As Object, ByVal e As System.EventArgs) Handles CBRele3.Click
        CBRele3.Enabled = False
        If CBRele3.CheckState = CheckState.Checked Then
            EnviarFrame(Comando.AtivarRele, Rele.Rele3)
            Led3.BackColor = Color.Lime
            EnviarFrame(Comando.SoliciatarStatusRele, Rele.Rele3)
        Else
            EnviarFrame(Comando.DesativarRele, Rele.Rele3)
            Led3.BackColor = Color.DarkGreen
            EnviarFrame(Comando.SoliciatarStatusRele, Rele.Rele3)
        End If
        CBRele3.Enabled = True
    End Sub

    Private Sub CBPingEnviar_CheckedChanged(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles CBPingEnviar.CheckedChanged
        If CBPingEnviar.CheckState = CheckState.Checked Then
            AbilitarBotoes(False, True)
            PingTemporizador.Enabled = True
        Else
            AbilitarBotoes(True, True)
            PingTemporizador.Enabled = False
        End If
    End Sub

    Private Sub BTConectar_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles BTConectar.Click
        'Inicia a conexão com o Hardware pela porta serial
        CBXPortas.Enabled = False

        If BTConectar.Text = "Desconectar" Then
            If CBPingEnviar.CheckState = CheckState.Checked Then
                CBPingEnviar.CheckState = CheckState.Unchecked
            End If
            CBRele1.CheckState = CheckState.Unchecked
            Led1.BackColor = Color.DarkGreen
            CBRele2.CheckState = CheckState.Unchecked
            Led2.BackColor = Color.DarkGreen
            CBRele3.CheckState = CheckState.Unchecked
            Led3.BackColor = Color.DarkGreen
            CBXPortas.Enabled = True
            BTConectar.Text = "Conectar"
            AbilitarBotoes(False, False)
            PortaSerial.Close()
            Return
        End If

        If PortaSerial.IsOpen Then PortaSerial.Close()

        If CBXPortas.Text = "" Then
            MsgBox("Porta serial não selecionada.", MsgBoxStyle.Critical, "Erro de conexão!")
            Return
        End If

        'Configura a porta serial
        PortaSerial.PortName = CBXPortas.Text
        PortaSerial.BaudRate = BAUDRATE
        PortaSerial.Parity = Parity.None
        PortaSerial.DataBits = 8
        PortaSerial.StopBits = StopBits.One
        PortaSerial.WriteTimeout = 42
        PortaSerial.ReadTimeout = 42

        'Abre a porta
        Try
            PortaSerial.Open()
        Catch ex As Exception
            MsgBox(ex.Message, MsgBoxStyle.Critical, "Erro de conexão!")
            CBXPortas.Enabled = True
            BTConectar.Enabled = True
        End Try


        If PortaSerial.IsOpen Then
            'Preenche a barra de estatus
            StatusPorta.Text = PortaSerial.PortName
            StatusBaudRate.Text = PortaSerial.BaudRate
            StatusConexao.Text = "Conectado"

            'Informa a conexão na tela de log
            MensagemLog(0, "Conectado", "Porta: " & PortaSerial.PortName & " BaudRate: " & PortaSerial.BaudRate)

            AbilitarBotoes(True, True)
            BTConectar.Text = "Desconectar"

            'Envia solicitação do estado dos reles
            EnviarFrame(Comando.SoliciatarStatusRele, Rele.Rele1)
            EnviarFrame(Comando.SoliciatarStatusRele, Rele.Rele2)
            EnviarFrame(Comando.SoliciatarStatusRele, Rele.Rele3)
        Else
            StatusPorta.Text = "***"
            StatusBaudRate.Text = "***"
            StatusConexao.Text = "Não conectado"
        End If
    End Sub

    Private Sub BTAtualizarSaidas_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles BTAtualizarSaidas.Click
        BTAtualizarSaidas.Enabled = False
        EnviarFrame(Comando.SoliciatarStatusRele, Rele.Rele1)
        EnviarFrame(Comando.SoliciatarStatusRele, Rele.Rele2)
        EnviarFrame(Comando.SoliciatarStatusRele, Rele.Rele3)
        BTAtualizarSaidas.Enabled = True
    End Sub

    'Sub-rotina para aguardar o recebimento do Frame de dados do Hardware
    Private Sub ReceberFrame()
        Dim Frame As StrFrame

        Dim CalcCheckSum As Byte

        'Timeout de recebimento: tempo máximo entre o recebimento do 1º Byte e o 5º Byte
        Dim TimeOut As Integer = 400
        Dim Tinicio As Date = Now
        Dim TAtual As TimeSpan

        'Laco para verificação de Timeout
        Do While PortaSerial.BytesToRead < 5
            TAtual = Now - Tinicio
            If TimeOut < TAtual.TotalMilliseconds Then
                PortaSerial.ReadExisting()
                MensagemLog(0, "TIMEOUT", "Tempo máximo de recebimento atingido:" & TAtual.TotalMilliseconds & " ms")
                Beep()
                Exit Sub
            End If
        Loop

        'Armazena os dados recebidos
        Frame.EndDestino = CType(PortaSerial.ReadByte(), Byte)
        Frame.EndRemetente = CType(PortaSerial.ReadByte(), Byte)
        Frame.Comando = CType(PortaSerial.ReadByte(), Byte)
        Frame.Dados = CType(PortaSerial.ReadByte(), Byte)
        Frame.CheckSum = CType(PortaSerial.ReadByte(), Byte)

        'Calcula o CheckSum dos dados recebidos
        CalcCheckSum = CalcularCheckSum(Frame)

        'Verifica o CheckSum recebido com o calculado
        If Frame.CheckSum <> CalcCheckSum Then
            MensagemLog(0, "RX: " + "Erro de CheckSum", String.Format("{0} - Recebido:{1:X2} Calculado:{2:X2}", Frame.ToString, Frame.CheckSum, CalcCheckSum))
            Beep()
            Return
        End If

        'Trata o camando recebido
        Dim CBSaida As New CheckBox
        Dim Led As New Microsoft.VisualBasic.PowerPacks.OvalShape

        Select Case Frame.Comando
            Case FormPrincipal.Comando.RespostaPing
            Case FormPrincipal.Comando.RespostaStatusReleAtivado
                Select Case Frame.Dados
                    Case 1 : CBSaida = CBRele1 : Led = Led1
                    Case 2 : CBSaida = CBRele2 : Led = Led2
                    Case 3 : CBSaida = CBRele3 : Led = Led3
                End Select
                If CBSaida.CheckState = CheckState.Unchecked Then
                    CBSaida.Checked = True
                    Led.BackColor = Color.Lime
                End If
            Case FormPrincipal.Comando.RespostaStatusReleDesativado
                Select Case Frame.Dados
                    Case 1 : CBSaida = CBRele1 : Led = Led1
                    Case 2 : CBSaida = CBRele2 : Led = Led2
                    Case 3 : CBSaida = CBRele3 : Led = Led3
                End Select
                If CBSaida.CheckState = CheckState.Checked Then
                    CBSaida.Checked = False
                    Led.BackColor = Color.DarkGreen
                End If
            Case Else
        End Select
        MensagemLog(0, "RX: " + Frame.Comando.ToString, "Frame recebido: " & Frame.ToString)
    End Sub

    'Sub-rotina para enviar o frame para o Hardware
    Private Sub EnviarFrame(ByVal Comando As Comando, ByVal Dados As Byte)
        Dim Frame As StrFrame

        Frame.EndDestino = EnderecoCliente
        Frame.EndRemetente = EnderecoServidor
        Frame.Comando = Comando
        Frame.Dados = Dados
        Frame.CheckSum = CalcularCheckSum(Frame)

        'Envia o comando pela serial
        PortaSerial.Write(Frame.ToBytes, 0, 5)

        Dim Tinicio As Date = Now
        Dim TAtual As TimeSpan
        'Aguarda receber os proprios dados transmitidos, com tempo maximo de recebimento
        Do While PortaSerial.BytesToRead < 5 And TAtual.TotalMilliseconds < 400
            TAtual = Now - Tinicio
            System.Threading.Thread.Sleep(1)
        Loop

        'Limpa os bytes recebidos, que são os que foram transmitidos
        Dim FrameEnvRec As StrFrame

        Try
            FrameEnvRec.EndDestino = PortaSerial.ReadByte()
            FrameEnvRec.EndRemetente = PortaSerial.ReadByte()
            FrameEnvRec.Comando = PortaSerial.ReadByte()
            FrameEnvRec.Dados = PortaSerial.ReadByte()
            FrameEnvRec.CheckSum = PortaSerial.ReadByte()
        Catch ex As Exception
        End Try

        'Mostra os bytes transmitidos e o retorno deles mesmos
        MensagemLog(0, "TX: " + Comando.ToString, "Frame enviado: " & Frame.ToString & " - " & FrameEnvRec.ToString)

        'Se for um comando que solicita resposta, chama a subrotina para receber os bytes
        If Comando = FormPrincipal.Comando.Ping Or Comando = FormPrincipal.Comando.SoliciatarStatusRele Then
            ReceberFrame()
        End If

        'Se houver bytes exedentes limpa o buffer da porta serial.
        If PortaSerial.BytesToRead > 0 Then
            PortaSerial.ReadExisting()
        End If
        'Aguarda 50 ms para que posso enviar novamente.
        System.Threading.Thread.Sleep(50)
    End Sub

    'Sub-rotina para calculo do CheckSum
    Private Function CalcularCheckSum(ByVal Frame As StrFrame) As Byte
        Dim CheckSumInt As Integer

        'Calcula a soma do 4 primeiros bytes.
        CheckSumInt = CType(Frame.EndDestino, Integer) + CType(Frame.EndRemetente, Integer) + CType(Frame.Comando, Integer) + CType(Frame.Dados, Integer)
        Return System.BitConverter.GetBytes(CheckSumInt)(0)
    End Function

    'Sub-rotina para Habilitar ou Desabilitar os botões.
    Private Sub AbilitarBotoes(ByVal BotaoReles As Boolean, ByVal BotaoPing As Boolean)
        CBRele1.Enabled = BotaoReles
        CBRele2.Enabled = BotaoReles
        CBRele3.Enabled = BotaoReles
        BTAtualizarSaidas.Enabled = BotaoReles
        CBPingEnviar.Enabled = BotaoPing
    End Sub

    'Sub-rotina que exibe o log de mensagens.
    Private Sub MensagemLog(ByVal Nivel As Integer, ByVal Titulo As String, ByVal Mensagem As String)
        Dim Msg As String = ""

        Select Case Nivel
            Case 0
                Msg = ">"
            Case 1
                Msg = ">  `-> "
            Case 2
                Msg = ">    `-> "
        End Select

        Msg &= "[" + Titulo + "] "
        Msg &= Mensagem + vbNewLine

        TBLog.Text &= Msg
        TBLog.SelectionStart = TBLog.Text.Length

        TBLog.SelectionLength = 0
        TBLog.ScrollToCaret()           'Rola o texto para a posição do cursor

        System.Windows.Forms.Application.DoEvents()     'Execulta os eventos de tela pendentes
    End Sub
End Class
